package m.appserver;

import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.util.AsciiString;
import m.common.action.Action;
import m.common.action.HtmlResult;
import m.common.action.VueResult;
import m.system.RuntimeData;
import m.system.lang.HtmlBodyContent;
import m.system.util.*;
import manage.util.excel.ExcelObject;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ResponseUtil {
	protected ResponseUtil(ChannelHandlerContext ctx,FullHttpRequest request,RequestUtil reqUtil) {
		this.ctx=ctx;
		this.request=request;
		response=new DefaultHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK);
		this.reqUtil=reqUtil;
	}
	private FullHttpResponse getFullResponse(String txt) {
		FullHttpResponse resp=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,Unpooled.wrappedBuffer(txt.getBytes(StandardCharsets.UTF_8)));
		resp.headers().add(response.headers());
		return resp;
	}
	private final HttpResponse response;
	private final ChannelHandlerContext ctx;
	private final FullHttpRequest request;
	private final RequestUtil reqUtil;
	private String contentType;
	private RandomAccessFile raf;
	public void setHeader(String name,Object value) {
		this.response.headers().set(AsciiString.cached(name), value);
	}
	public HttpHeaders headers() {
		return this.response.headers();
	}

	/**
	 * 手动设置返回的内容类型
	 */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	private String getContentType(String suffix) {
		if(!StringUtil.isSpace(this.contentType)){
			return this.contentType;
		}
		suffix=suffix.toUpperCase();
		switch (suffix) {
			case "TXT":
				return "text/plain";
			case "CSS":
				return "text/css";
			case "HTML":
				return "text/html";
			case "JS":
				return "application/javascript";
			case "JSON":
				return "application/json";
			case "PNG":
				return "image/png";
			case "JPG":
				return "image/jpeg";
			case "GIF":
				return "image/gif";
			case "XLSX":
				return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			case "XLS":
				return "application/vnd.ms-excel";
			default:
				return "*/*";
		}
	}

	/**
	 * 添加公共的header属性  暂时写法
	 */
	private void addCommonHeader(){
		setHeader("x-content-options","nosniff");
		setHeader("x-content-type-options","nosniff");
		setHeader("x-frame-options","SAMEORIGIN");
		setHeader("x-xss-protection","1; mode=block");
	}

	private String toSetCookie() {
		String cookie=StringUtil.noSpace(request.headers().get(HttpHeaderNames.COOKIE));
		Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookie);
		for(Cookie ck : cookies) {
			if(ck.name().equals(Action.SESSION_NAME)) {
				return ClientCookieEncoder.STRICT.encode(ck)+";HttpOnly=true";
			}
		}
		return "";
	}
//	public void redirectFilter(String redirectPath,JSONObject requestBody,Map<String,File> fileMap) throws UnsupportedEncodingException {
//		FullHttpResponse res=getFullResponse("");
//		res.setStatus(HttpResponseStatus.PERMANENT_REDIRECT); //设置重定向响应码 （临时重定向、永久重定向）
//		HttpHeaders headers = res.headers();
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "x-requested-with,content-type");
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "POST,GET");
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
//		headers.set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
//		headers.set(HttpHeaderNames.LOCATION, redirectPath); //重定向URL设置
//		ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
//	}

	/**
	 * 重定向执行Action
	 */
	public Object redirect(ActionRedirect ar) {
		Class<? extends Action> clazz=ar.getAction();
		String method=ar.getMethod();
		Class<?>[] params=null;
		try {
			if (null != ar.getJson())
				ActionUtil.fillJSONObject(reqUtil.getRequestJson(), ar.getJson());
			Action action = reqUtil.getRequestJson().toJavaObject(clazz);
			action.setRequest(reqUtil);
			action.setResponse(this);
			action.setAuthorization(reqUtil.getHeader("Authorization"));
			//查找action定义的请求注解,并返回参数名称和类型map
			Map<String, Class<?>> paramTypeMap = ActionUtil.getRequestParamList(action.getClass(), method, ar.getUriMethod().name());
			params=paramTypeMap.values().toArray(new Class<?>[]{});
			action.setFileMap(reqUtil.getFiles());
			SystemUtil.println(reqUtil.getRequestBody());
			//执行action的方法-----------------------------------
			Object result = ClassUtil.executeMethod(action, method, params, toMethodParams(paramTypeMap, reqUtil.getRequestJson()));
			if (result instanceof ActionRedirect) {//重定向类
				result = redirect((ActionRedirect) result);
			}
			return result;
		}catch (Exception e){
			if(null!=params){
				String error;
				if(e instanceof InvocationTargetException){
					error=((InvocationTargetException)e).getTargetException().getMessage();
				}else{
					error=e.getMessage();
				}
				Method m=ClassUtil.getDeclaredMethods(clazz,method,params);
				if(null!=m) {
					String type = m.getAnnotatedReturnType().getType().getTypeName();
					if(type.equals(JSONMessage.class.getName())){
						return new JSONMessage()
						.setStatus(ServerResultStatus.EXCEPTION)
						.push("msg",error);
					}else if(type.equals(VueResult.class.getName())){
						return VueResult.toExceptionPage(error,true);
					}else if(type.equals(HtmlResult.class.getName())){
						return HtmlResult.toErrorPage(error);
					}else if(type.equals(HtmlBodyContent.class.getName())){
						return new HtmlBodyContent(error).setStatus(ServerResultStatus.EXCEPTION);
					}
				}
			}
			return e;
		}
	}
	private Object[] toMethodParams(Map<String,Class<?>> paramTypeMap, JSONObject requestBody) {
		List<Object> ls=new ArrayList<>();
		for(String key : paramTypeMap.keySet()) {
			Class<?> clazz=paramTypeMap.get(key);
			Object obj=null;
			if(requestBody != null) {
				obj=requestBody.get(key);
			}
			if(!clazz.equals(String.class)) {
				if(null!=obj&&StringUtil.isSpace(obj.toString())) {
					obj=null;
				}
			}
			ls.add(ObjectUtil.convert(clazz, obj));
		}
		return ls.toArray(new Object[] {});
	}

	/**
	 * 输出html   text/html
	 */
	public void outHtml(String html) throws UnsupportedEncodingException {
		outTxt(HttpResponseStatus.OK,html,"HTML");
	}

	/**
	 * 输出txt   text/plain
	 */
	public void outTxt(HttpResponseStatus status,String txt) throws UnsupportedEncodingException {
		outTxt(status,txt,"TXT");
	}
	/**
	 * 输出txt   自定义返回类型
	 */
	public void outTxt(HttpResponseStatus status,String txt,String suffix) {
		FullHttpResponse res=getFullResponse(txt);
		res.setStatus(status);
		HttpHeaders heads = res.headers();
		heads.add(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
		heads.add(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes()); // 3
		heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
		heads.set(HttpHeaderNames.SET_COOKIE,toSetCookie());
		addCommonHeader();
		ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
	}

	/**
	 * 输出路径文件
	 */
	public void outPath(String path) throws UnsupportedEncodingException {
		String p= URLDecoder.decode(path,"UTF-8");
		File file;
		if(FileUtil.existsWebFile(p)) {//寻找web目录下文件
			file=new File(RuntimeData.getClassWeb()+p);
		}else if(FileUtil.existsPublicFile(p)) {//寻找公共web目录下文件
			file=new File(RuntimeData.getPublicWeb()+p);
		}else {//不存在则找临时目录
			String ps=RuntimeData.getClassTmp()+p;
			file=new File(ps);
			if(!file.exists()) {
				try {//临时目录不存在则从jar包内拷贝到临时目录
					InputStream is=this.getClass().getClassLoader().getResourceAsStream("web"+(p.indexOf("/")==0?"":"/")+p);
					if(null!=is) FileUtil.writeFile(ps, is);
				} catch (Exception e) {//不存在则404
					outTxt(HttpResponseStatus.NOT_FOUND, p+" Not Found");
					return;
				}
			}
		}
		outFile(file,p.lastIndexOf(".")>=0?p.substring(p.lastIndexOf(".")+1):"");
	}

	/**
	 * 输出excel
	 */
	public void outExcel(ExcelObject excel) throws UnsupportedEncodingException {
		try {
			raf = new RandomAccessFile(excel.toExcelFile(), "r");
			long fileLength = raf.length();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType("xlsx")+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, "attachment; filename="+URLEncoder.encode(excel.getName(),"UTF-8")+".xlsx");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			addCommonHeader();
			ctx.write(response);
			ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
		} catch (Exception e) {
			e.printStackTrace();
			outTxt(HttpResponseStatus.NOT_FOUND,e.getMessage());
		}
	}

	/**
	 * 输出文件
	 */
	public void outFile(File file,String suffix) throws UnsupportedEncodingException {
		try {
			raf = new RandomAccessFile(file, "r");
			long fileLength = raf.length();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			addCommonHeader();
			ctx.write(response);
			ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
		} catch (Exception e) {
			outTxt(HttpResponseStatus.NOT_FOUND,"no file!");
		}
	}

	/**
	 * 输出图片
	 */
	public void outBufferedImage(BufferedImage image,String suffix) throws UnsupportedEncodingException {
		try {
			ByteArrayOutputStream os=new ByteArrayOutputStream();
			ImageIO.write(image, suffix, os);
			byte[] bs=os.toByteArray();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, bs.length);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			addCommonHeader();
			ctx.write(response);
			ByteBuf bb=Unpooled.wrappedBuffer(bs);
			ctx.write(bb);
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
			os.close();
			bb.release();
		} catch (Exception e) {
			outTxt(HttpResponseStatus.NOT_FOUND,"no image!");
		}
	}
}
